package cn.bugstack.mybatis.binding;

import cn.bugstack.mybatis.session.SqlSession;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @Description 映射器代理类
 * @Classname MapperProxy
 * @Date 2024/3/27 20:23
 * @Created by aazhao
 */
/**
 * 代理类，用于动态生成Mapper接口的实现类。
 * @param <T> Mapper接口的类型。
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;

    /**
     * 当前的SqlSession对象，用于执行SQL语句和管理事务。
     * SqlSession是MyBatis中与数据库交互的主要接口，提供了执行SQL、获取结果集、管理事务等一系列功能。
     */
    private SqlSession sqlSession;

    /**
     * 代表一个泛型接口的Class类型，该接口是映射器接口，用于定义数据库操作的方法。
     * 这个成员变量是一个泛型类的类型引用，用来确保这个类只能处理特定映射器接口的实例。
     *
     * @param <T> 映射器接口的类型参数。
     */
    private final Class<T> mapperInterface;

    /**
     * 缓存映射器方法的映射关系。
     * 该映射用于缓存Method（方法）到MapperMethod（映射器方法）的映射关系，以提高后续方法调用的效率。
     * Map的键为Method对象，即Java方法的反射对象。
     * Map的值为MapperMethod对象，代表一个映射器方法，用于执行具体的数据库操作。
     */
    private final Map<Method, MapperMethod> methodCache;

    /**
     * 构造函数，用于创建MapperProxy实例。
     * 此构造函数负责初始化MapperProxy对象，将当前的SqlSession、需要代理的Mapper接口以及方法缓存传入，
     * 为后续方法调用提供必要的环境和数据支持。
     *
     * @param sqlSession 当前的SqlSession对象，提供与数据库交互的能力。
     * @param mapperInterface 需要代理的Mapper接口，即待实现的接口，通过代理方式动态实现其方法。
     * @param methodCache 存储方法与MapperMethod映射的关系，用于快速查找和执行对应的SQL操作。
     */
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    /**
     * 当调用代理对象的方法时，此方法会被触发。实现了Invocation接口的invoke方法，用于动态代理的调用处理。
     *
     * @param proxy 代理对象本身，即被代理的目标对象。
     * @param method 被调用的方法，指明了当前代理对象上被调用的具体方法。
     * @param args 方法调用时的参数数组，包含了调用方法时传入的所有参数。
     * @return 方法的返回值，根据实际调用的方法不同，返回值类型可能会有所不同。
     * @throws Throwable 方法执行中抛出的异常，如果方法执行过程中出现异常，则会抛出给调用者。
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 判断被调用的方法是否为Object类的方法，如equals、hashCode等，若是则直接调用。
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        else {
            // 执行SQL会话中的selectOne方法，动态调用Mapper接口方法。
            // 这里核心逻辑是通过mapperMethod来执行具体的数据库操作。
            final MapperMethod mapperMethod = cachedMapperMethod(method);
            return mapperMethod.execute(sqlSession, args);
        }
    }



    /**
     * 缓存 Mapper 方法。如果方法已经缓存，则直接返回缓存中的 Mapper 方法；如果未缓存，则创建新的 Mapper 方法并加入缓存。
     *
     * @param method 待缓存的 Mapper 接口方法
     * @return 缓存中或新创建的 Mapper 方法实例
     */
    private MapperMethod cachedMapperMethod(Method method) {
        // 尝试从缓存获取 Mapper 方法，如果不存在则创建新的 MapperMethod 实例
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
            // 创建新的 MapperMethod 实例并加入缓存
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }

}
